package roborally;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;
import roborally.Exceptions.IllegalDestinationException;


/**
 * A class of batteries, that extends the class carryable. Batteries have an energy, maxEnergy and a weight.
 * 
 * @invar  	| Energy.isValidEnergyAmount(this.getEnergy(),this.getMaxEnergy())
 * @invar  	| Energy.isValidMaxEnergyAmount(this.getMaxEnergy(),standardMaxEnergyAmount)
 * 
 * @version  2.90
 * @author   Michiel Ruelens & Dieter Naten
 */
public class Battery extends Carryable {
	
	private Energy energy;
	private Energy maxEnergy;
	private final EnergyUnit standardEnergyUnit = EnergyUnit.WATTSECOND;
	private final double standardMaxEnergyAmount = 5000;
	//Inherits 'boolean isBeingCarried'
	//Inherits 'long x'
	//Inherits 'long y'
	//Inherits 'Board board'
	//Inherits 'String type'

	/**
     * Initialize the new battery with given initialEnergy and weight.
     * 
     * @param  	initialEnergy
     * 		   	The initial energy of the new battery.
     * @param  	weight
     * 		   	The weight of the new battery.
     * @pre   	| Energy.isValidEnergyAmount(initialEnergy, standardEnergyUnit, getMaxEnergy())
     * @effect	| setPlaceableType(PlaceableType.BATTERY)
     * @effect	| setMaxEnergy(new Energy(standardMaxEnergyAmount,standardEnergyUnit))
     * @effect	| setEnergy(new Energy(initialEnergy,standardEnergyUnit))
     */
	@Raw
	public Battery(double initialEnergy, int weight){
		super(weight); //Sets terminated status to false
		setPlaceableType(PlaceableType.BATTERY);
		setMaxEnergy(new Energy(standardMaxEnergyAmount,standardEnergyUnit));
		assert(Energy.isValidEnergyAmount(initialEnergy, standardEnergyUnit, getMaxEnergy()));
		setEnergy(new Energy(initialEnergy,standardEnergyUnit));
	}
	
	/**
	 * This method puts the current battery on a board.
	 * 
	 * @param 	board
	 * 		  	The board on which the battery should be placed
	 * @param	 x
	 * 		  	The x-coordinate on which the battery should be placed
	 * @param 	y
	 * 		 	 The y-coordinate on which the battery should be placed
	 * @effect | putPlaceable(board,x,y)
	 */
	public void putBattery(Board board, long x, long y){
		putPlaceable(board,x,y);
	}
	
	/**
	 * Return the current energy of this battery.
	 */
	@Basic
	public Energy getEnergy() {
		return energy;
	}

	/**
	 * This method sets the energy of a battery to the given energy.
	 * 
	 * @param 	energy
	 * 			The energy this battery is to be given.
	 * @pre		| Energy.isValidEnergyAmount(energy.getNumeral(),energy.getUnit(),getMaxEnergy())
	 * @post	| (new this).getEnergy() == energy
	 */	
	public void setEnergy(Energy energy) {
		assert(Energy.isValidEnergyAmount(energy.getNumeral(),energy.getUnit(),getMaxEnergy()));
		this.energy = energy;
	}
	
	/**
	 * Return the current maxEnergy of this battery.
	 */
	@Basic
	public Energy getMaxEnergy() {
		return maxEnergy;
	}

	/**
	 * This method sets the maxEnergy of a battery to the given maxEnergy.
	 * 
	 * @param 	maxEnergy
	 * 			The maxEnergy this battery is to be given.
	 * @pre		| Energy.isValidMaxEnergyAmount(maxEnergy.getNumeral(),maxEnergy.getUnit()
	 * @post	| (new this).getMaxEnergy() == maxEnergy
	 */
	public void setMaxEnergy(Energy maxEnergy) {
		assert(Energy.isValidMaxEnergyAmount(maxEnergy,standardMaxEnergyAmount));
		this.maxEnergy = maxEnergy;
	}

	/**
	 * Return the standard Energy unit of this battery.
	 */
	@Basic
	public EnergyUnit getStandardEnergyUnit() {
		return standardEnergyUnit;
	}
	
	/**
	 * This method is executed when a battery is hit. The energy of the battery is increased, if possible.
	 *
	 * @effect 	| currentEnergy = this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral()
	 * 			| if (currentEnergy+500 <= this.standardMaxEnergyAmount) 
	 * 			|	{this.setEnergy(new Energy(currentEnergy +500,EnergyUnit.WATTSECOND))}
	 * 			| else if(currentEnergy+500 < 0)
	 * 			| 	{this.setEnergy(new Energy(this.standardMaxEnergyAmount,EnergyUnit.WATTSECOND))}
	 */	
	@Override
	public void hit(){
		double currentEnergy = this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral();
		if(currentEnergy+500 <= this.standardMaxEnergyAmount){
			Energy newEnergy1 = new Energy(currentEnergy+500,EnergyUnit.WATTSECOND);
			this.setEnergy(newEnergy1);
		}
		else if(currentEnergy+500 > this.standardMaxEnergyAmount){
			Energy newEnergy2 = new Energy(this.standardMaxEnergyAmount,EnergyUnit.WATTSECOND);
			this.setEnergy(newEnergy2);
		}
		System.out.println("A battery has been hit, it's energy has increased with 500 Ws!");
		System.out.println("It now has" + this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral() + " Ws!");
	}
	
	/**
	 * Method executed when a robot uses a battery.
	 * 
	 * @param   robot
	 * 			The robot that uses the current battery
	 * @pre		| !this.isTerminated()
	 * @pre		| robot.getAllPossessions().contains(this)
	 * @effect 	| currentAddEnergy = robot.getEnergyObject().toUnit(EnergyUnit.WATTSECOND).getNumeral()
	 * 			|						+ this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral()
	 * 			| if (currentAddEnergy<= robot.maximumMaxEnergyAmountInWS) 
	 * 			|	{	 robot.recharge(this.getEnergy().toUnit(EnergyUnit.WATTSECOND).getNumeral()))
	 *			|		 this.setEnergy(new Energy(0,this.getEnergy().getUnit()))}
	 * 			| else if(currentAddEnergy>robot.maximumMaxEnergyAmountInWS)
	 * 			| 	{	 robot.setEnergy(robot.getMaxEnergy().toUnit(robot.getEnergyObject().getUnit()))
	 * 			|	 	 this.setEnergy(new Energy((robotEnergy.getNumeral()+batteryEnergy.getNumeral())-robotMaxEnergy.getNumeral()
	 * 			|									,this.getEnergy().getUnit()))}
	 */
	//Used by the method useCarryable(Carryable carryable) and only invoked if all conditions are met.
	@Override
	public void useCarryable(Robot robot){
		assert(!this.isTerminated());
		assert(robot.getAllPossessions().contains(this));
		EnergyUnit originalBatteryUnit = this.getEnergy().getUnit();
		EnergyUnit originalRobotUnit = robot.getEnergyObject().getUnit();
		Energy batteryEnergy = this.getEnergy().toUnit(EnergyUnit.WATTSECOND);
		Energy robotEnergy = robot.getEnergyObject().toUnit(EnergyUnit.WATTSECOND);
		Energy robotMaxEnergy = robot.getMaxEnergy().toUnit(EnergyUnit.WATTSECOND);		
		double currentAddEnergy = robotEnergy.getNumeral()+batteryEnergy.getNumeral();	
			//Battery is already depleted
			if(this.getEnergy().getNumeral()==0){
				System.out.println("That battery is out of energy. However it remains in your inventory to annoy you with it's weight.");
			}
			//The robot is already at full energy
			else if(robotEnergy.getNumeral()==robotMaxEnergy.getNumeral()){
				System.out.println("The robot is already at full energy and thus does not require a recharge.");
			}
			//simple recharge
			else if(currentAddEnergy<=robotMaxEnergy.getNumeral()){
				robot.recharge(batteryEnergy.getNumeral());
				this.setEnergy(new Energy(0,originalBatteryUnit));
				System.out.println("Robot <"+robot+"> got a recharge of: "+batteryEnergy.toUnit(EnergyUnit.WATTSECOND).getNumeral()+" Ws.");
			}
			//Battery has more energy than needed to fully recharge
			else if(currentAddEnergy>robotMaxEnergy.getNumeral()){
				double excessAmount = (robotEnergy.getNumeral()+batteryEnergy.getNumeral())-robotMaxEnergy.getNumeral(); 
				robot.setEnergy(robot.getMaxEnergy().toUnit(originalRobotUnit));
				this.setEnergy(new Energy(excessAmount,originalBatteryUnit));
				System.out.println("Robot <"+robot+"> got a recharge of: "+(robotMaxEnergy.getNumeral()-robotEnergy.getNumeral())+" Ws.");
				System.out.println("The battery has "+excessAmount+" Ws of energy left.");
			}
	}
}
